

import java.util.ArrayList;
import java.util.Random;

/**
 *
 * @authors cnavarro, dverstraete
 */
public class Joueur implements InterfaceJoueur{

    private Couleur maCouleur;
    private TypeJoueur type;
    private InterfacePlateau plateau;
    private int dimensionPlateau;
    private int heuristique;

    /**
     * Constructeur par défaut
     */
    public Joueur()
    {
        maCouleur = Couleur.VIDE;
        type = TypeJoueur.AUTOMATE;
        plateau = null;
        dimensionPlateau = 8;
        this.heuristique = 1;
    }

     /**
     * Constructeur de copie
     */
    public Joueur(Couleur couleur, Plateau InterfacePlateau, TypeJoueur joueur, int heuristique)
    {
        maCouleur = couleur;
        type = joueur;
        plateau = InterfacePlateau;
        dimensionPlateau = InterfacePlateau.getDimension();
        this.heuristique = heuristique;
    }

    /**
     * Retourne un coup si le joueur est automate en fonction de l'heuristique
     * @return 
     */
    public Coup joue() {

        ArrayList<Coup> coupsPossibles = this.getCoupsPossibles(this.plateau.getMatricePlateau());
        
        Coup coupAJouer = null;
        
        switch(this.heuristique){
            case 1 : //Aléatoire
            {
                // random sur les coups possibles
                if(!coupsPossibles.isEmpty()){
                    Random rdm = new Random();
                    coupAJouer = coupsPossibles.get(rdm.nextInt(coupsPossibles.size()));
                }
                break;
            }
            case 2 : //Facile
            {
                //construction d'un arbre de profondeur 1, calcul du prochain coup
                Arbre<CoupPossible> arbre = buildArbre(1);
                Noeud<CoupPossible> noeudMeilleurCoup = minMax(arbre.racine);
                coupAJouer = noeudMeilleurCoup.getInfo().getCoup();
                break;
            }
            case 3 : //Difficile
            {
                //construction d'un arbre de profondeur 3, l'automate a 2 coups d'avance sur l'adversaire
                Arbre<CoupPossible> arbre = buildArbre(3);
                Noeud<CoupPossible> noeudMeilleurCoup = minMax(arbre.racine);
                coupAJouer = noeudMeilleurCoup.getInfo().getCoup();
                break;
            }
                
        }
        
        return coupAJouer;
    }
    
        /** 
     * Retourne tous les coups possibles du joueur sur le "plateau" passé en paramètre
     */
    private ArrayList<Coup> getCoupsPossibles(Couleur[][] plateau){
        ArrayList<Coup> coupsPossibles = new ArrayList<Coup>();
        ArrayList<Coup> coupsAdverses = getPionsAdverses();

        Coup coupActuel = null;
        int colonneAdverse = 0;
        int ligneAdverse = 0;
        
        //pour tous les coups les adverses
        for(Coup coupAdverse : coupsAdverses){
            
            colonneAdverse = coupAdverse.getColonne();
            ligneAdverse = coupAdverse.getLigne();
            
            //pour chaque ligne
            for(int i=ligneAdverse-1; i<=ligneAdverse+1; i++){
                //pour chaque colonne
                for(int j=colonneAdverse-1; j<=colonneAdverse+1; j++){
                    
                    coupActuel = new Coup(i, j);
                    
                    //si le coup actuel n'est pas le coup adverse sélectionné
                    if(!(j==colonneAdverse
                            && i==ligneAdverse)){
                        //si le coup des jouable
                        if(isCoupPossible(coupAdverse, coupActuel, plateau)){
                            coupsPossibles.add(coupActuel);
                        }
                    }
                }
            }
        }

        System.out.print("Coups possibles : ");
        for(Coup coup : coupsPossibles){
            System.out.print("(" + coup.getLigne() + "," + coup.getColonne() + ") ");
        }
        System.out.print("\n");
        
        return coupsPossibles;
    }

    /** 
     * Retourne la liste des pions adverses 
     */
    private ArrayList<Coup> getPionsAdverses(){
        ArrayList<Coup> pionsAdverses = new ArrayList<Coup>();

        Couleur[][] plateauBuffer = this.plateau.getMatricePlateau();

        Couleur couleurOpposee = (this.maCouleur.equals(Couleur.BLANC))? Couleur.NOIR : Couleur.BLANC;

        for(int i=0; i<this.dimensionPlateau; i++){
            for(int j=0; j<this.dimensionPlateau; j++){
                if(plateauBuffer[i][j].equals(couleurOpposee)){
                    pionsAdverses.add(new Coup(i, j));
                }
            }
        }
        return pionsAdverses;
    }

    /**
     * coupAdverse et coup doivent etre adjacents et distincts
     * @param coupAdverse : coup de l'adversaire
     * @param coup : coup à tester
     * @return vrai si coup est un coup possible, faux sinon
     */
    private boolean isCoupPossible(Coup coupAdverse, Coup coup, Couleur[][] plateauBuffer)
    {
        boolean isPossible = false;
        

        //si le coup est dans le plateau
        if(isCoupDansPlateau(coup)){
            
            int ligneAdverse = coupAdverse.getLigne();
            int colonneAdverse = coupAdverse.getColonne();
            int ligneActuelle = coup.getLigne();
            int colonneActuelle = coup.getColonne();

            //si le coup n'est pas déjà joué
            if(plateauBuffer[ligneActuelle][colonneActuelle].equals(Couleur.VIDE)){

                Couleur couleurOpposee = (this.maCouleur.equals(Couleur.BLANC))? Couleur.NOIR : Couleur.BLANC;

                //indice de ligne du prochain coup
                int ligProcCoup = ligneAdverse + (ligneAdverse - ligneActuelle);
                //indice de colonne du prochain coup
                int colProcCoup = colonneAdverse + (colonneAdverse - colonneActuelle);
                //prochain coup à jouer
                Coup procCoup = new Coup(ligProcCoup, colProcCoup);

                //si le prochain coup est toujours dans le plateau
                if(isCoupDansPlateau(procCoup)){

                    int ligneBuffer = ligneAdverse;
                    int colBuffer = colonneAdverse;

                    while(isCoupDansPlateau(new Coup(ligProcCoup, colProcCoup))
                            && plateauBuffer[ligProcCoup][colProcCoup].equals(couleurOpposee)){

                        int a = ligProcCoup;
                        int b = colProcCoup;

                        //indice de ligne du prochain coup
                        ligProcCoup = ligProcCoup + (ligProcCoup - ligneBuffer);
                        //indice de colonne du prochain coup
                        colProcCoup = colProcCoup + (colProcCoup - colBuffer);

                        ligneBuffer = a;
                        colBuffer = b;
                    }

                    //si le prochain coup est en dehors du plateau
                    if(!isCoupDansPlateau(new Coup(ligProcCoup, colProcCoup))){
                        isPossible = false;
                    } else {
                        //si le prochain coup est de ma couleur : gagné
                        if(plateauBuffer[ligProcCoup][colProcCoup].equals(this.maCouleur)){
                            isPossible = true;
                        } else {
                            //si le prochain coup est de la couleur vide : perdu
                            if(plateauBuffer[ligProcCoup][colProcCoup].equals(Couleur.VIDE)){
                                isPossible = false;
                            }
                        }
                    }
                }
            }
        }

        return isPossible;
    }

    /** 
     * Retourne vrai si coup est dans le plateau de jeu, faux sinon
     * @param coup
     * @return 
     */
    private boolean isCoupDansPlateau(Coup coup){
        return coup.getColonne()<this.dimensionPlateau && coup.getColonne()>=0
                && coup.getLigne()<this.dimensionPlateau && coup.getLigne()>=0;
    }
    
    /** 
     * Construit l'arbre de résolution de l'algorithme 
     * Les noeuds contiennent des CoupPossible
     */
    private Arbre<CoupPossible> buildArbre(int profondeur){
        
        Arbre a = null;

        a = new Arbre<CoupPossible>();

        // Initialisation de la racine
        CoupPossible coupRacine = new CoupPossible(null, plateau.getMatricePlateau(), 0);
        a.initRacine(coupRacine);
        buildNoeud(a, profondeur, this.plateau.getMatricePlateau());

        return a;
    }
    
    /** 
     * Construit un noeud de l'arbre de résolution 
     * @param : ancienPlateau est le plateau du noeud père
     */
    private void buildNoeud(Arbre<CoupPossible> a, int profondeur, Couleur[][] ancienPlateau)
    {

        if(profondeur > 0)
        {
            ArrayList<Coup> coupsPossibles = this.getCoupsPossibles(ancienPlateau);
            for(int i=0; i<coupsPossibles.size(); i++){
                
                Couleur[][] plateauActuel = affecteCoup(ancienPlateau, coupsPossibles.get(i));
                
                CoupPossible coupPossible = 
                        new CoupPossible(coupsPossibles.get(i), plateauActuel, 0);
                
                //valeur de l'heuristique
                if((profondeur - 1) == 0)
                {
                    coupPossible.setHeuristique(calculHeuristique(plateauActuel));
                }
                
                a.addFils(coupPossible);
                a.gotoFils(i);
                buildNoeud(a, profondeur-1, plateauActuel);
            }
            if(!a.isRacine()){
                a.gotoPere();
            }
        } else {
            if(!a.isRacine()){
                a.gotoPere();
            }
        }
    }
    
    /**
     * Retourne un plateau avec le coup "coup" joué dans le paramètre "plateau"
     * @param plateau
     * @param coup
     * @return 
     */
    private Couleur[][] affecteCoup(Couleur[][] plateau, Coup coup)
    {
        Couleur[][] plateauBuffer = new Couleur[this.dimensionPlateau][this.dimensionPlateau];
        int ligneCoup = coup.getLigne();
        int colonneCoup = coup.getColonne();
        
        //copie du plateau dans un buffer
        for(int i= 0; i < this.dimensionPlateau;i++)
        {
            plateauBuffer[i] = (Couleur[])plateau[i].clone();
        }

        // On affecte la couleur au plateau
        plateauBuffer[ligneCoup][colonneCoup] = this.maCouleur;
        
        for(int i=ligneCoup-1; i<=ligneCoup+1; i++){
                //pour chaque colonne
                for(int j=colonneCoup-1; j<=colonneCoup+1; j++){
                    
                    Coup caseAPrendre = new Coup(i, j);
                    
                    //si la prochaine case n'est pas mon coup
                    if(!(caseAPrendre.getColonne()==colonneCoup
                            && caseAPrendre.getLigne()==ligneCoup)){
                        
                        //si le prochain coup est dans le plateau et est un coup possible
                        if(isCoupDansPlateau(caseAPrendre) && isCoupPossible(caseAPrendre, coup, plateau)){

                            Couleur couleurOpposee = (this.maCouleur.equals(Couleur.BLANC))? Couleur.NOIR : Couleur.BLANC;

                            //prochain coup
                            int ligCaseAPrendre = caseAPrendre.getLigne();
                            int colCaseAPrendre = caseAPrendre.getColonne();
                            
                            //coup
                            int ligCoupActuel = ligneCoup;
                            int colCoupActuel = colonneCoup;

                            while(isCoupDansPlateau(new Coup(ligCaseAPrendre, colCaseAPrendre))
                                    && plateauBuffer[ligCaseAPrendre][colCaseAPrendre].equals(couleurOpposee)){

                                //change la couleur du coup adverse
                                plateauBuffer[ligCaseAPrendre][colCaseAPrendre] = this.maCouleur;
                                        
                                int a = ligCaseAPrendre;
                                int b = colCaseAPrendre;

                                //prochain coup
                                ligCaseAPrendre = ligCaseAPrendre + (ligCaseAPrendre - ligCoupActuel);
                                colCaseAPrendre = colCaseAPrendre + (colCaseAPrendre - colCoupActuel);
                                
                                //coup actuel
                                ligCoupActuel = a;
                                colCoupActuel = b;
                            }   
                        }
                    }
                }
            }
        

        return plateauBuffer;
    }
    
    
    /**
     * Retourne la valeur de l'heuristique :
     * l'heuristique retourne le nombre de pions du joueur moins le nombre de pions de son adversaire
     * @param plateau
     * @return
     */
    private Integer calculHeuristique(Couleur[][] plateau) {
        return (this.getNbMesPions(plateau)-this.getNbPionsAdverses(plateau));
    }

      /**
       * Retourne le nombre de pions adverses
       * @return
       */
    private int getNbPionsAdverses(Couleur[][] plateauBuffer){
        int nbPionsAdverses = 0;

        Couleur couleurOpposee = (this.maCouleur.equals(Couleur.BLANC))? Couleur.NOIR : Couleur.BLANC;

        for(int i=0; i<this.dimensionPlateau; i++){
            for(int j=0; j<this.dimensionPlateau; j++){
                if(plateauBuffer[i][j].equals(couleurOpposee)){
                    nbPionsAdverses++;
                }
            }
        }
        return nbPionsAdverses;
    }

       /**
       * Retourne le nombre de mes pions
       * @return
       */
    private int getNbMesPions(Couleur[][] plateauBuffer){
        int nbMesPions = 0;

        Couleur maCouleur = (this.maCouleur.equals(Couleur.BLANC))? Couleur.BLANC : Couleur.NOIR;

        for(int i=0; i<this.dimensionPlateau; i++){
            for(int j=0; j<this.dimensionPlateau; j++){
                if(plateauBuffer[i][j].equals(maCouleur)){
                    nbMesPions++;
                }
            }
        }
        return nbMesPions;
    }
    
    /**
     * Algorithme du minimax
     * @param noeud
     * @return
     */
    public Noeud<CoupPossible> minMax(Noeud<CoupPossible> noeud)
    {

        Noeud noeudMax = noeud.getFils().get(0);

        for(Noeud noeudFils : noeud.getFils())
        {
            CoupPossible itemMax = (CoupPossible) noeudMax.getInfo();
            Integer valMinFils = valMin(noeudFils);
            if(valMinFils > itemMax.getHeuristique())
            {
                noeudMax = noeudFils;
                CoupPossible item = (CoupPossible)noeudFils.getInfo();
                item.setHeuristique(valMinFils);
                noeudMax.setInfo(item);
            }
        }
        return noeudMax;
    }
    
    /**
     * Retourne le maximum des minimums de la valeur heuristique des fils de "noeud"
     * @param noeud
     * @return
     */
    public Integer valMax (Noeud<CoupPossible> noeud)
    {
        Integer heuristique = 0;
        CoupPossible item = noeud.getInfo();

        if(noeud.isNoeudFeuille())
        {
            heuristique = item.getHeuristique();
        } else {
            ArrayList valeursFils = new ArrayList<Integer>();
            for(Noeud noeudFils : noeud.getFils())
            {
                valeursFils.add(valMin(noeudFils));
            }
            heuristique = maxValue(valeursFils);
        }
        return heuristique;
    }

    /**
     * Retourne le minimum des maximums de la valeur heuristique des fils de "noeud"
     * @param noeud
     * @return
     */
    public Integer valMin (Noeud<CoupPossible> noeud)
    {
        Integer heuristique = 0;
        CoupPossible item = noeud.getInfo();

        if(noeud.isNoeudFeuille())
        {
            heuristique = item.getHeuristique();
        } else {
            ArrayList valeursFils = new ArrayList<Integer>();
            for(Noeud noeudFils : noeud.getFils())
            {
                valeursFils.add(valMax(noeudFils));
            }
            heuristique = minValue(valeursFils);
        }
        return heuristique;
    }

    /**
     * Valeur minimale de l'arraylist passé en paramètre
     * @param valeursFils
     * @return
     */
    public Integer minValue(ArrayList<Integer> valeursFils)
    {
        Integer valMin = valeursFils.get(0);
        for(Integer heuristique : valeursFils)
        {
            valMin = (heuristique < valMin)? heuristique : valMin;
        }
        return valMin;
    }

    /**
     * Valeur maximale de l'arraylist passé en paramètre
     * @param valeursFils
     * @return
     */
    public Integer maxValue(ArrayList<Integer> valeursFils)
    {
        Integer valMax = valeursFils.get(0);;
        for(Integer heuristique : valeursFils)
        {
            valMax = (heuristique > valMax)? heuristique : valMax;
        }
        return valMax;
    }
    


    
    
    
    
    /** Getter */
    public TypeJoueur getType() {
        return this.type;
    }

    public Couleur getCouleur() {
        return this.maCouleur;
    }
}